/*
 * ==========================================================================================================
 * 
 *                                               Custom physics demo
 *                                              
 * ==========================================================================================================
 * 
 * The demo shows various usages of physics:
 * - controllers: for character movements, custom physics
 * - time control: pause / play / accelerate / reduce (cannot return in the past like in movies)
 * - 
 * 
 * 
 * 
 * - Engine control
 * Espace - quit
 * F1 - view console
 * F2 - toggle fullscreen mode
 * F3 - antialiasing
 * F4 - view node model
 * F5 - view collisions
 * F6 - collision fillmode
 * F7 - view partitioner
 * 
 * - Demo control
 * W - move forward
 * S - move backward
 * A - strafe left
 * D - strafe right
 * Left shift - move down
 * Space - move up
 * Mouse move - look
 */

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace CustomPhysics
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {

        #region fields  

        private DARE.CDare m_Dare;
        private DARE.CScene m_Scene;
        private DARE.CRender3D m_Render;

        private DARE.CFirstPersonCamera m_firstCam;
        private DARE.CIsoCamera m_isoCam;
        private Vector3 m_moveCam;

        private bool m_rightButtonPressed;
        private bool m_leftButtonPressed;

        private DARE.CNode m_characterNode;
        private DARE.CCharacterController m_characterController;
        private DARE.CNode m_characterPointer;

        private DARE.CNode m_nParent0;
        private DARE.CNode m_nParent1;
        private DARE.CNode m_nChild;

        private DARE.CNode m_nPivot;
        private List<DARE.CNode> m_nChildren;
        private bool m_ignorePhysicsTransform;

        #endregion

        #region ctor

        public Game1()
        {
            m_Dare = new DARE.CDare(this);
        }

        protected override void Initialize()
        {
            m_moveCam = Vector3.Zero;
            m_rightButtonPressed = false;
            m_leftButtonPressed = false;
            m_nChildren = new List<DARE.CNode>();
            m_ignorePhysicsTransform = false;
            base.Initialize();
        }

        #endregion

        #region load

        protected override void LoadContent()
        {
            m_Dare.Config.General.WindowTitle = "Debug Raycast and Physics";
            m_Dare.Config.General.ShowMouseCursor = true;
            m_Dare.Config.General.ShowDebugConsole = true;

            //m_Dare.Config.Video.SetResolution(1024, 768);
            m_Dare.Config.Video.FullScreen = false;
            m_Dare.Config.Video.SetResolution(1366, 768);
            //m_Dare.Config.Video.FullScreen = true;
            m_Dare.Config.Video.MultiSampling = true;
            m_Dare.Config.Video.UnlockFps = true;

            m_Dare.RockNRoll();

            CreateRender();
            CreateCamera();
            CreateScene();
        }

        #endregion

        #region creation

        private void CreateRender()
        {
            m_Scene = new DARE.CScene("scene");
            m_Render = new DARE.CRender3D("render", new DARE.COctree(100));

            m_Scene.RegisterRenderer(m_Render);
        }

        private void CreateCamera()
        {
            m_firstCam = new DARE.CFirstPersonCamera("firstCam");
            m_firstCam.Translate(new Vector3(-25, 10, 25));
            m_firstCam.LookAt(new Vector3(-5, 0, 5));

            m_isoCam = new DARE.CIsoCamera("isoCam");
            m_isoCam.FollowPosition = new Vector3(-12, 15, 12);
            m_isoCam.ZoomMin = 0.2f;
            m_isoCam.Zoom = 0.6f;

            

            m_Render.CameraMgr.RegisterCamera(m_isoCam);
            m_Render.CameraMgr.RegisterCamera(m_firstCam);

            m_firstCam.IsRendering = false;
        }

        private void CreateScene()
        {
            // show option
            m_Render.ShowModelNode = false;
            m_Render.ShowCollision = false;
            m_Render.Partitioner.Show = false;
            m_Render.CollisionFillMode = FillMode.WireFrame;

            // light
            m_Render.EffectMgr.LighteningEnabled = true;
            m_Render.EffectMgr.AmbientLightColor = Color.White;
            m_Render.EffectMgr.AmbientLightIntensity = 0.1f;

            m_Render.EffectMgr.DirectionalLight0Direction = new Vector3(0.6f, -1, -0.8f);
            m_Render.EffectMgr.DirectionalLight0DiffuseColor = Color.White;
            m_Render.EffectMgr.DirectionalLight0DiffuseIntensity = 1.5f;

            // stop the time
            //m_Dare.Config.Time.IsPaused = true;

            // ------------------------ create the world -------------------------------

            // create the floor model
            DARE.CNode floor = m_Render.CreateNode("nFloor", DARE.EType.STATIC, true);
            DARE.CNode child = null;

            floor.TranslateY(-0.5f);
            floor.Entity.CreatePhysicBox(40, 1, 40);
            floor.Entity.Physic.CollisionType = DARE.APhysicObject.ECollisionType.GROUND;
            floor.Entity.Physic.Body.Immovable = true;
            floor.AABBFromWorldBoundingBox = floor.Entity.Physic.Skin.WorldBoundingBox;
            floor.Entity.CreateBasicBox(40, 1, 40);
            ((DARE.CBasicModel)floor.Entity.Model).Color = Color.Blue;

            DARE.CNode nPlane = m_Render.CreateNode("nPlane", DARE.EType.STATIC, true);
            nPlane.TranslateY(-30);
            nPlane.Entity.CreatePhysicBox(200, 1, 200);
            nPlane.Entity.Physic.CollisionType = DARE.APhysicObject.ECollisionType.NONE;
            nPlane.Entity.Physic.Body.Immovable = true;
            nPlane.AABBFromWorldBoundingBox = nPlane.Entity.Physic.Skin.WorldBoundingBox;

            m_characterNode = m_Render.CreateNode("nCharacter", DARE.EType.DYNAMIC, true).Translate(-13, 3, 13);
            m_characterNode.Entity.CreatePhysicCapsule(2, 1);
            m_characterNode.Entity.Physic.CollisionType = DARE.APhysicObject.ECollisionType.NONE;
            m_characterNode.AABBFromWorldBoundingBox = m_characterNode.Entity.Physic.Skin.WorldBoundingBox;
            m_characterNode.Entity.CreateBasicCapsule(2, 1);

            m_characterController = new DARE.CCharacterController(m_characterNode);
            m_characterController.MoveSpeed = 3;

            m_characterPointer = m_Render.CreateNode("nCharPointer", DARE.EType.DYNAMIC, true);
            m_characterPointer.Entity.CreateBasicCylinder(0.05f, 0.7f);
            ((DARE.CBasicModel)m_characterPointer.Entity.Model).Color = Color.Red;
            m_characterPointer.Entity.Model.Orientation = Matrix.Identity;

            DARE.CNode n45 = m_Render.CreateNode("n45", DARE.EType.DYNAMIC, true);
            n45.Translate(-5, 1.35f, 11);
            n45.Roll(MathHelper.PiOver4);
            n45.Entity.CreatePhysicBox(4, 0.1f, 4);
            n45.Entity.Physic.CollisionType = DARE.APhysicObject.ECollisionType.GROUND;
            n45.Entity.Physic.Body.Immovable = true;
            n45.AABBFromWorldBoundingBox = n45.Entity.Physic.Skin.WorldBoundingBox;
            n45.Entity.CreateBasicBox(4, 0.1f, 4);

            DARE.CNode nWall = m_Render.CreateNode("nWall", DARE.EType.STATIC, true);
            nWall.Translate(0, 2.5f, 8.5f);
            nWall.Entity.CreatePhysicBox(8, 5, 1);
            nWall.Entity.Physic.CollisionType = DARE.APhysicObject.ECollisionType.UNDEFINED;
            nWall.Entity.Physic.Body.Immovable = true;
            nWall.AABBFromWorldBoundingBox = nWall.Entity.Physic.Skin.WorldBoundingBox;
            nWall.Entity.CreateBasicBox(8, 5, 1);

            SpawnCubes(new Vector3(10, 0.5f, -5), 3, 3, 3);
            SpawnPhysicsCubes(new Vector3(-10, 0.5f, -10), 3, 4, 3);

            //m_Render.SkyMgr.CreateSkybox("Sky", "CubeMaps/Ct/ct");

            // restore the time
            //m_Dare.Config.Time.IsPaused = false;

            DARE.CNode nLeft = m_Render.CreateNode("nLeft", DARE.EType.STATIC, true);
            nLeft.Translate(-30, -0.5f, 0);
            nLeft.Entity.CreatePhysicBox(20, 1, 20);
            nLeft.Entity.Physic.CollisionType = DARE.APhysicObject.ECollisionType.GROUND;
            nLeft.Entity.Physic.Body.Immovable = true;
            nLeft.AABBFromWorldBoundingBox = nLeft.Entity.Physic.Skin.WorldBoundingBox;
            nLeft.Entity.CreateBasicBox(20, 1, 20);

            DARE.CNode nDown = m_Render.CreateNode("nDown", DARE.EType.STATIC, true);
            nDown.Translate(-10, -1.5f, -30);
            nDown.Entity.CreatePhysicBox(20, 1, 20);
            nDown.Entity.Physic.CollisionType = DARE.APhysicObject.ECollisionType.GROUND;
            nDown.Entity.Physic.Body.Immovable = true;
            nDown.AABBFromWorldBoundingBox = nDown.Entity.Physic.Skin.WorldBoundingBox;
            nDown.Entity.CreateBasicBox(20, 1, 20);

            DARE.CNode nStep = m_Render.CreateNode("nStep", DARE.EType.STATIC, true);
            nStep.Translate(-5, -0.5f, -21.43f);
            nStep.Pitch(-0.3f);
            nStep.Entity.CreatePhysicBox(3, 0.1f, 3);
            nStep.Entity.Physic.CollisionType = DARE.APhysicObject.ECollisionType.GROUND;
            nStep.Entity.Physic.Body.Immovable = true;
            nStep.AABBFromWorldBoundingBox = nStep.Entity.Physic.Skin.WorldBoundingBox;
            nStep.Entity.CreateBasicBox(3, 0.1f, 3);

            // ---------------------- test to change parent ----------------------------

            m_nParent0 = m_Render.CreateNode("nParent0", DARE.EType.STATIC, true);
            m_nParent1 = m_Render.CreateNode("nParent1", DARE.EType.STATIC, true);

            m_nParent0.SetPosition(2, 2, 0);
            m_nParent1.SetPosition(0, 2, -5);

            m_nChild = m_nParent0.CreateChild("nChild", DARE.EType.STATIC, true);
            m_nChild.Translate(3, 0, -1);

            m_nParent0.Entity.CreateBasicBox(Vector3.One / 2);
            m_nParent1.Entity.CreateBasicBox(Vector3.One / 2);
            m_nChild.Entity.CreateBasicSphere(0.5f);

            // ----------------------- test physicalyzed nodes -------------------------

            m_nPivot = m_Render.CreateNode("nPivot", DARE.EType.DYNAMIC, true).Translate(-27, 3, 0).Rotate(0, 0.1f, 0.4f);

            for (int i = 0; i < 13; ++i)
            {
                DARE.CNode current = m_nPivot.CreateChild().Yaw(i * 0.48f).TranslateX(3, DARE.CNode.ESpace.LOCAL);
                DARE.CNode chi = current.CreateChild(DARE.EType.DYNAMIC);

                chi.Smooth = true;
                chi.SmoothAmount = 0.01f;

                chi.Entity.CreateBasicBox();
                chi.Entity.CreatePhysicBox();

                m_nChildren.Add(current);
            }

            // -------------------------------------------------------------------------

            // set the follow node to the iso camera
            m_isoCam.FollowNode = m_characterNode;
        }

        private void SpawnCubes(Vector3 pos, int width, int height, int depth)
        {
            DARE.CNode node = null;

            for (int y = 0; y < height; ++y)
                for (int x = 0; x < width; ++x)
                    for (int z = 0; z < depth; ++z)
                    {
                        node = m_Render.CreateNode().Translate(pos.X + x, pos.Y + y, pos.Z + z, DARE.CNode.ESpace.WORLD);
                        node.Entity.CreatePhysicBox(0.95f, 0.95f, 0.95f);
                        node.Entity.Physic.CollisionType = DARE.APhysicObject.ECollisionType.FADE;
                        node.Entity.Physic.Body.Immovable = true;

                        node.Entity.CreateBasicBox(0.95f, 0.95f, 0.95f);
                        node.Entity.Model.FadingValue = 0.2f;
                        node.Entity.Model.FadingSpeed = 10.0f;
                        ((DARE.CBasicModel)node.Entity.Model).Color = Color.BurlyWood;
                    }
        }

        private void SpawnPhysicsCubes(Vector3 pos, int width, int height, int depth)
        {
            DARE.CNode node = null;

            for (int y = 0; y < height; ++y)
                for (int x = 0; x < width; ++x)
                    for (int z = 0; z < depth; ++z)
                    {
                        node = m_Render.CreateNode(DARE.EType.DYNAMIC).Translate(pos.X + x, pos.Y + y, pos.Z + z, DARE.CNode.ESpace.WORLD);
                        node.Entity.CreatePhysicBox(0.95f, 0.95f, 0.95f);
                        node.Entity.Physic.CollisionType = DARE.APhysicObject.ECollisionType.UNDEFINED;
                        node.Entity.Physic.Body.SetBodyInvInertia(0, 0, 0);

                        node.Entity.CreateBasicBox(0.95f, 0.95f, 0.95f);
                        node.Entity.Model.IsTransparent = true;
                        node.Entity.Model.Transparency = 0.5f;
                    }
        }

        #endregion

        #region run

        protected override void Update(GameTime gameTime)
        {
            ProcessKeyPressed();
            ProcessKeyReleased();
            ProcessMousePressed();
            ProcessMouseReleased();
            ProcessMouseMoved();
            ProcessMouseWheeled();
            ProcessRayCastCameraToMouse();
            ApplyMovement();

            float distFromGround = m_characterController.DistanceFromGround;

            m_Dare.DebugMgr.DebugConsole.WriteLine("distance from ground = " + distFromGround.ToString());

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);
        }

        private void ApplyMovement()
        {
            float fixedTime = m_Dare.Config.Time.FixedTimeSinceLastFrame;
            float fixedMoveSpeed = fixedTime / 100.0f;

            if (m_rightButtonPressed && m_firstCam.IsActive)
                m_firstCam.Translate(m_moveCam * fixedMoveSpeed);
            m_characterPointer.IsVisible = m_characterController.IsMoving;

            if (!m_ignorePhysicsTransform)
                return;
            m_nPivot.Yaw(0.01f);
            m_nPivot.Pitch(0.006f);
            foreach (DARE.CNode node in m_nChildren)
                node.Roll(0.015f);
        }

        private void ProcessRayCastCameraToMouse()
        {
            bool exit = false;

            foreach (DARE.CRayCast.CIntersectInfo iInfo in m_Render.RayCast.IntersectAllFromCenterToMouse(100, 10))
            {
                switch (iInfo.node.Entity.Physic.CollisionType)
                {
                    case DARE.APhysicObject.ECollisionType.FADE:
                        if (iInfo.node.Entity.Model != null) iInfo.node.Entity.Model.Fade(); break;

                    case DARE.APhysicObject.ECollisionType.GROUND:
                        if (m_leftButtonPressed)
                        {
                            m_characterController.MoveTo(iInfo.pos);
                            m_characterPointer.Position = iInfo.pos;
                        }
                        exit = true;
                        break;

                    default: break;
                }

                m_Dare.DebugMgr.DebugConsole.WriteLine("Object hit: " + iInfo.node.Name);

                if (exit)
                    break;
            }
            m_Dare.DebugMgr.DebugConsole.WriteLine("");
        }

        #endregion

        #region input

        private void ProcessKeyPressed()
        {
            foreach (Keys key in m_Dare.InputMgr.Keyboard.KeyPressedEvent)
                switch (key)
                {
                    case Keys.Escape: this.Exit(); break;
                    case Keys.F1: m_Dare.DebugMgr.DebugConsoleEnabled = !m_Dare.DebugMgr.DebugConsoleEnabled; break;
                    case Keys.F2: m_Dare.Config.Video.FullScreen = !m_Dare.Config.Video.FullScreen; break;
                    case Keys.F3: m_Dare.Config.Video.MultiSampling = !m_Dare.Config.Video.MultiSampling; break;
                    case Keys.F4: m_Render.ShowModelNode = !m_Render.ShowModelNode; break;
                    case Keys.F5: m_Render.ShowCollision = !m_Render.ShowCollision; break;
                    case Keys.F6: m_Render.CollisionFillMode = m_Render.CollisionFillMode == FillMode.WireFrame ? FillMode.Solid : FillMode.WireFrame; break;
                    case Keys.F7: m_Render.Partitioner.Show = !m_Render.Partitioner.Show; break;                    

                    case Keys.P: m_Dare.Config.Time.IsPaused = !m_Dare.Config.Time.IsPaused; break;
                    case Keys.C:
                        if (!m_isoCam.IsActive) m_Render.CameraMgr.ActiveCamera = m_isoCam;
                        else m_Render.CameraMgr.ActiveCamera = m_firstCam;
                        break;
                    case Keys.E: m_characterController.Jump(30); break;

                        // change parent and keep the actual position
                    case Keys.I:
                        if (m_nChild.Parent.Equals(m_nParent0))
                            m_nChild.SetParent(m_nParent1, true);
                        else
                            m_nChild.SetParent(m_nParent0, true);
                        m_nChild.TranslateX(1);
                        m_nChild.TranslateX(-1);
                        break;
                        // change parent
                    case Keys.O:
                        if (m_nChild.Parent.Equals(m_nParent0))
                            m_nChild.SetParent(m_nParent1);
                        else
                            m_nChild.SetParent(m_nParent0);
                        m_nChild.TranslateX(1);
                        m_nChild.TranslateX(-1);
                        break;

                    //case Keys.U:
                    //    m_ignorePhysicsTransform = !m_ignorePhysicsTransform;

                    //    if (m_ignorePhysicsTransform)
                    //    {
                    //        foreach (DARE.CNode node in m_nChildren)
                    //        {
                    //            foreach (DARE.CNode child in node.Children.Values)
                    //            {
                    //                child.IgnorePhysicsTransform = true;
                    //                child.Position = node.Position;
                    //                child.Orientation = node.Orientation;
                    //            }
                    //        }
                    //    }
                    //    else
                    //    {
                    //        foreach (DARE.CNode node in m_nChildren)
                    //        {
                    //            foreach (DARE.CNode child in node.Children.Values)
                    //            {
                    //                child.IgnorePhysicsTransform = false;
                    //            }
                    //        }
                    //    }
                    //    break;

                    case Keys.U:
                        m_ignorePhysicsTransform = !m_ignorePhysicsTransform;

                        if (m_ignorePhysicsTransform)
                        {
                            foreach (DARE.CNode node in m_nChildren)
                            {
                                foreach (DARE.CNode child in node.Children.Values)
                                {
                                    child.IgnorePhysicsTransform = true;
                                    child.Position = node.Position;
                                    child.Orientation = node.Orientation;
                                }
                            }
                        }
                        else
                        {
                            foreach (DARE.CNode node in m_nChildren)
                            {
                                foreach (DARE.CNode child in node.Children.Values)
                                {
                                    child.IgnorePhysicsTransform = false;
                                }
                            }
                        }
                        break;

                    case Keys.W: m_moveCam.Z -= 1; break;
                    case Keys.S: m_moveCam.Z += 1; break;
                    case Keys.A: m_moveCam.X -= 1; break;
                    case Keys.D: m_moveCam.X += 1; break;
                    case Keys.LeftShift: m_moveCam.Y -= 1; break;
                    case Keys.Space: m_moveCam.Y += 1; break;

                    default: break;
                }
        }

        private void ProcessKeyReleased()
        {
            foreach (Keys key in m_Dare.InputMgr.Keyboard.KeyReleasedEvent)
                switch (key)
                {
                    case Keys.W: m_moveCam.Z += 1; break;
                    case Keys.S: m_moveCam.Z -= 1; break;
                    case Keys.A: m_moveCam.X += 1; break;
                    case Keys.D: m_moveCam.X -= 1; break;
                    case Keys.LeftShift: m_moveCam.Y += 1; break;
                    case Keys.Space: m_moveCam.Y -= 1; break;

                    default: break;
                }
        }

        private void ProcessMousePressed()
        {
            foreach (DARE.CMouse.EButton button in m_Dare.InputMgr.Mouse.ButtonPressedEvent)
            {
                switch (button)
                {
                    case DARE.CMouse.EButton.RIGHT: m_rightButtonPressed = true; break;
                    case DARE.CMouse.EButton.LEFT: m_leftButtonPressed = true; break;

                    default: break;
                }
                m_Dare.InputMgr.Mouse.IsCentered = m_rightButtonPressed;
                m_Dare.Config.General.ShowMouseCursor = !m_rightButtonPressed;
            }
        }

        private void ProcessMouseReleased()
        {
            foreach (DARE.CMouse.EButton button in m_Dare.InputMgr.Mouse.ButtonReleasedEvent)
            {
                switch (button)
                {
                    case DARE.CMouse.EButton.RIGHT: m_rightButtonPressed = false; break;
                    case DARE.CMouse.EButton.LEFT: /*m_characterController.Stop();*/ m_leftButtonPressed = false; break;

                    default: break;
                }
                m_Dare.InputMgr.Mouse.IsCentered = m_rightButtonPressed;
                m_Dare.Config.General.ShowMouseCursor = !m_rightButtonPressed;
            }
        }

        private void ProcessMouseMoved()
        {
            Vector2 mouseMoved = m_Dare.InputMgr.Mouse.MoveEvent.Movement;

            if (m_Dare.InputMgr.Mouse.HasMoved && m_rightButtonPressed && m_firstCam.IsActive)
                m_firstCam.Rotate(new Vector3(-mouseMoved.Y / 400.0f, -mouseMoved.X / 400.0f, 0.0f));
        }

        private void ProcessMouseWheeled()
        {
            foreach (DARE.CMouseWheelEvent wheel in m_Dare.InputMgr.Mouse.WheelEvent)
            {
                if (wheel.WheelValue > 0 && m_isoCam.IsActive)
                    m_isoCam.Zoom -= 0.05f;
                else if (wheel.WheelValue < 0 && m_isoCam.IsActive)
                    m_isoCam.Zoom += 0.05f;
            }
        }

        #endregion

    }
}
